import math
import tools.dms_degrees
import pandas as pd
import utils
import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import interp1d


class BendingCurveCalculator:
    """
    alpha: 度分秒以元组输入
    涉及的角度单位为弧度
    所有的要素全部用实例变量获取，不需要从外部调用任何函数
    """

####
    def __init__(self, R, l0, alpha=None, JD=None, delta_dis=None, ZD_XY=None, JD_XY=None, ZH_XY=None, T1_XY=None,
                 T2_XY=None,
                 print_attributes=False, draw_curve=False):
        self.R = R
        self.l0 = l0
        if alpha:
            self.alpha = tools.dms_degrees.dms_to_degrees(*alpha) * math.pi / 180
            self.beta0, self.delta0, self.b0, self.x0, self.y0, self.p, self.m, self.T, self.L, self.E0, self.q = self._calculate_bending_curve_element()
        if JD:
            self.JD = JD
            self.ZH, self.HY, self.QZ, self.YH, self.HZ = self._calculate_bending_curve_dis()
        if delta_dis:
            self.deflection_angle_method_df = self._deflection_angle_method(delta_dis)
            if ZD_XY and JD_XY and T1_XY:
                self.polar_coordinate_method_df, self.X, self.Y, self.remark_list = self._polar_coordinate_method(ZD_XY,
                                                                                                                  JD_XY,
                                                                                                                  ZH_XY,
                                                                                                                  T1_XY,
                                                                                                                  T2_XY)
                if draw_curve:
                    self._plot_bending_curve()
        if print_attributes:
            self._print_attributes()

    def _calculate_beta_delta_b(self, l):
        """
        计算缓和曲线对应的beta、delta、b弧度值
        """
        beta = l ** 2 / (2 * self.R * self.l0)
        delta = l ** 2 / (6 * self.R * self.l0)  # delta1 : delta2 = l1^2 : l2^2
        b = l ** 2 / (3 * self.R * self.l0)  # beta : b : delta = 3 : 2 : 1,
        return beta, delta, b

    def _calculate_x_y(self, l):
        """
        计算缓和曲线点的坐标
        """
        x = l - l ** 5 / (40 * self.R ** 2 * self.l0 ** 2)
        y = l ** 3 / (6 * self.R * self.l0)
        return x, y

    def _calculate_bending_curve_element(self):
        """
        计算缓和曲线曲线要素
        """
        beta0, delta0, b0 = self._calculate_beta_delta_b(self.l0)
        x0, y0 = self._calculate_x_y(self.l0)
        p = y0 / 4
        m = self.l0 / 2 - self.l0 ** 3 / (240 * self.R ** 2)
        T = m + (self.R + p) * math.tan(self.alpha / 2)
        L = 2 * self.l0 + ((self.alpha - 2 * beta0) / (2 * math.pi)) * self.R * 2 * math.pi
        E0 = (self.R + p) / math.cos(beta0) - self.R
        q = 2 * T - L
        return beta0, delta0, b0, x0, y0, p, m, T, L, E0, q

    def _calculate_bending_curve_dis(self):
        """
        计算缓和曲线的各个主点的里程
        """
        ZH = self.JD - self.T
        HY = ZH + self.l0
        QZ = HY + (self.L - 2 * self.l0) / 2
        YH = QZ + (self.L - 2 * self.l0) / 2
        HZ = YH + self.l0
        if HZ - (self.JD + self.T - self.q) > 0.0000001:
            print("HZ里程检核值：", HZ - (self.JD + self.T - self.q))
            raise ValueError("HZ里程检核值异常")
        return ZH, HY, QZ, YH, HZ

    def _print_attributes(self):
        """
        打印实例变量
        """
        for k, v in vars(self).items():
            if k == 'deflection_angle_method_df' or k == 'polar_coordinate_method_df':
                print()
                print(k, ': ')
                print(v)
                print()
            elif k == 'X' or k == 'Y' or k == 'remark_list':
                continue
            else:
                print(k, ': ', v)

    def _plot_bending_curve(self):
        """
        根据X、Y坐标绘制缓和曲线
        """

        plt.rcParams['font.sans-serif'] = ['SimHei']  # 指定默认字体为黑体
        plt.rcParams['axes.unicode_minus'] = False
        self.X = [float(i) for i in self.X]
        self.Y = [float(i) for i in self.Y]
        # 创建一个插值函数
        f = interp1d(self.X, self.Y, kind='cubic')  # 'cubic'代表三次插值

        # 生成更多的X坐标点以绘制平滑曲线
        x_smooth = np.linspace(self.X[0], self.X[-1], 300)  # 在最小和最大X坐标之间生成300个点

        # 使用插值函数计算新的Y坐标点
        y_smooth = f(x_smooth)

        # 使用plt.plot绘制平滑曲线
        plt.plot(x_smooth, y_smooth, label='测设曲线')

        # 绘制原始数据点
        plt.plot(self.X, self.Y, 'o', label='中桩坐标点')
        for i in range(len(self.remark_list)):
            plt.text(self.X[i], self.Y[i], self.remark_list[i], ha='right', va='bottom', fontsize=10)
        # 添加图例
        plt.legend()

        # 添加标题和标签
        plt.title('极坐标法测设曲线')
        plt.xlabel('X')
        plt.ylabel('Y')

        # 显示网格（非必须）
        plt.grid(True)

        # 显示图表
        plt.show()

    def _deflection_angle_method(self, delta_dis):
        """
        偏角法测设
        """
        # 缓和曲线段
        if delta_dis > self.l0:
            raise ValueError("delta_dis大于缓和曲线长度")
        last_delta_dis = (self.HY - self.ZH) % delta_dis
        num_delta_dis = int((self.HY - self.ZH) // delta_dis)
        delta_list = [0, self._calculate_beta_delta_b(delta_dis)[1]]
        points_dis_list = [self.ZH + i * delta_dis for i in range(num_delta_dis + 1)]
        for i in range(num_delta_dis - 1):
            delta_list.append((i + 2) ** 2 * delta_list[1])
        if last_delta_dis:
            num_delta_dis += 1
            delta_list.append(self.delta0)
            points_dis_list.append(self.HY)
        # 圆曲线段
        y_last_delta_dis = (self.QZ - self.HY) % delta_dis
        y_num_delta_dis = int((self.QZ - self.HY) // delta_dis)
        for i in range(y_num_delta_dis):
            delta_list.append(delta_list[-1] + delta_dis / self.R / 2)
        y_points_dis_list = [self.HY + i * delta_dis for i in range(1, y_num_delta_dis + 1)]
        if y_last_delta_dis:
            delta_list.append(delta_list[-1] + y_num_delta_dis / self.R / 2)
            y_points_dis_list.append(self.QZ)
        points_dis_list.extend(y_points_dis_list)

        remark_list = ["" for _ in range(len(points_dis_list))]
        for index, dis in enumerate(points_dis_list):
            if dis == self.ZH:
                remark_list[index] = "ZH"
            if dis == self.HY:
                remark_list[index] = "HY"
            if dis == self.QZ:
                remark_list[index] = "QZ"
        dis_delta_list = ['']
        for i in range(len(delta_list)):
            delta_list[i] = tools.dms_degrees.degrees_to_dms(delta_list[i] / math.pi * 180)
            points_dis_list[i] = utils.new_round(points_dis_list[i], 2)
            if i > 0:
                dis_delta_list.append(float(points_dis_list[i]) - float(points_dis_list[i - 1]))
        da_df = pd.DataFrame(
            {"里程   ": points_dis_list, "偏角     ": delta_list, "距离    ": dis_delta_list, "备注    ": remark_list})
        # print("偏角法测设结果：\n", da_df)
        return da_df

    def _polar_coordinate_method(self, ZD_XY, JD_XY, ZH_XY, T1_XY, T2_XY):
        """
        极坐标法测设,输入坐标都以(x,y)形式输入
        """
        main_points_dis = [self.ZH, self.HY, self.QZ, self.YH, self.HZ]
        main_points_name = ["ZH", "HY", "QZ", "YH", "HZ"]
        # 合并两个列表为字典
        main_points = dict(zip(main_points_name, main_points_dis))
        # 生成等差数列
        points_dis_list = list(np.arange(main_points_dis[0], main_points_dis[2], delta_dis))
        points_dis_list.append(main_points_dis[2])

        XY = [ZH_XY]
        xy = [(0, 0)]
        alpha_ZH_JD_XY = math.atan2(JD_XY[1] - ZH_XY[1], JD_XY[0] - ZH_XY[0])
        alpha_ZH_i_XY = [alpha_ZH_JD_XY]
        # 计算各点xy坐标
        for i in range(1, len(points_dis_list)):
            l = points_dis_list[i] - points_dis_list[0]
            if points_dis_list[i] <= main_points["QZ"]:
                xy.append((l - l ** 5 / (40 * R ** 2 * l0 ** 2), l ** 3 / (6 * R * l0)))
            else:
                fai = (l - l0) / R + self.beta0
                xy.append((R * math.sin(fai) + self.m, R * (1 - math.cos(fai)) + self.p))
        # 计算各点在xy坐标系中的到ZH点的距离，计算各点在xy坐标系中的偏角，计算各点在XY坐标系中的坐标
        dis_ZH_i_xy = []
        ceta_ZH_i_xy = []
        for i in range(len(xy)):
            dis_ZH_i_xy.append(math.sqrt(xy[i][0] ** 2 + xy[i][1] ** 2))
            ceta_ZH_i_xy.append(math.atan2(xy[i][1], xy[i][0]))
        for i in range(1, len(ceta_ZH_i_xy)):
            alpha_ZH_i_XY.append(alpha_ZH_i_XY[0] + ceta_ZH_i_xy[i])
        for i in range(1, len(dis_ZH_i_xy)):
            XY.append((XY[0][0] + dis_ZH_i_xy[i] * math.cos(alpha_ZH_i_XY[i]),
                       XY[0][1] + dis_ZH_i_xy[i] * math.sin(alpha_ZH_i_XY[i])))
        # 计算方位角和水平距离
        alpha_i_T1_XY = []
        dis_i_T1_XY = []
        for i in range(len(XY)):
            alpha_i_T1_XY.append(math.atan2(XY[i][1] - T1_XY[1], XY[i][0] - T1_XY[0]))
            dis_i_T1_XY.append(math.sqrt((XY[i][0] - T1_XY[0]) ** 2 + (XY[i][1] - T1_XY[1]) ** 2))
        alpha_i_T1_XY = [tools.dms_degrees.degrees_to_dms(i * 180 / math.pi) for i in alpha_i_T1_XY]
        # 处理数据
        for i in range(len(XY)):
            XY[i] = list(XY[i])
            XY[i][0] = utils.new_round(XY[i][0], 3)
            XY[i][1] = utils.new_round(XY[i][1], 3)
            dis_i_T1_XY[i] = utils.new_round(dis_i_T1_XY[i], 3)
        remark_list = ["" for _ in range(len(points_dis_list))]
        for index, dis in enumerate(points_dis_list):
            if dis == self.ZH:
                remark_list[index] = "ZH"
            if dis == self.HY:
                remark_list[index] = "HY"
            if dis == self.QZ:
                remark_list[index] = "QZ"
        X, Y = [i[0] for i in XY], [i[1] for i in XY]

        result = pd.DataFrame(
            {'里程    ': points_dis_list, 'X    ': X, 'Y    ': Y, '方位角     ': alpha_i_T1_XY,
             '水平距离     ': dis_i_T1_XY,
             '备注      ': remark_list})
        return result, X, Y, remark_list


if __name__ == '__main__':
    R = 100
    l0 = 15
    alpha = (25, 46, 59)
    JD = 754.287
    delta_dis = 5
    ZD_XY = (1466.706, 4078.773)
    JD_XY = (2577.732, 7008.773)
    ZH_XY = (2564.504, 6981.392)
    T1_XY = (2565.406, 6980.958)
    BendingCurveCalculator(R, l0, alpha, JD, delta_dis, ZD_XY, JD_XY, ZH_XY, T1_XY, print_attributes=True)